using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using MIMPWinForms.MIMPGameServices;
using MIMPGameLibrary;
using MIMPWinForms.SpriteDisplays;
using System.Windows.Forms;


namespace MIMPWinForms.XnaControls
{
    public class MIMPGameScreenControl : GraphicsDeviceControl, IGameScreenEventsService
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        #region IGameScreenEventsService events and raisers

        /// <summary>
        /// Occurs when a HexDisplay is clicked on with the left mouse button. 
        /// PrimaryEventData is the Hex associated with the clicked HexDisplay.
        /// SecondaryEventData is a Keys flag indicating whether Shift, Control, and/or Alt were held down while clicking.
        /// </summary>
        public event EventHandler<EventArgs<Hex, Keys>> HexLeftClicked;
        /// <summary>
        /// Occurs when a HexDisplay is clicked on with the right mouse button. 
        /// PrimaryEventData is the Hex associated with the clicked HexDisplay.
        /// SecondaryEventData is a Keys flag indicating whether Shift, Control, and/or Alt were held down while clicking.
        /// </summary>
        public event EventHandler<EventArgs<Hex, Keys>> HexRightClicked;
        /// <summary>
        /// Occurs when a HexDisplay is double-clicked on with the left mouse button. 
        /// PrimaryEventData is the Hex associated with the clicked HexDisplay.
        /// SecondaryEventData is a Keys flag indicating whether Shift, Control, and/or Alt were held down while clicking.
        /// </summary>
        public event EventHandler<EventArgs<Hex, Keys>> HexDoubleClicked;


        protected void RaiseHexLeftClicked(Hex hex, Keys modifiers) {
            if (HexLeftClicked != null) {
                HexLeftClicked(this, new EventArgs<Hex, Keys>(hex, modifiers));
            }
        }
        protected void RaiseHexRightClicked(Hex hex, Keys modifiers) {
            if (HexRightClicked != null) {
                HexRightClicked(this, new EventArgs<Hex, Keys>(hex, modifiers));
            }
        }
        protected void RaiseHexDoubleClicked(Hex hex, Keys modifiers) {
            if (HexDoubleClicked != null) {
                HexDoubleClicked(this, new EventArgs<Hex, Keys>(hex, modifiers));
            }
        }
        #endregion

        ContentManager content;
        SpriteBatch spriteBatch;

        List<Hex> hexes;

        /// <summary>
        /// Used to display the Hexes that make up the game map
        /// </summary>
        List<HexDisplay> _hexDisplays;
        /// <summary>
        /// Used to display the Monsters in the game
        /// </summary>
        List<MonsterDisplay> _monsterDisplays;
        /// <summary>
        /// A small sprite in the shape of a flag used to indicate the presence of a ControlPoint.
        /// </summary>
        Texture2D controlPointSprite;
        /// <summary>
        /// A partially transparent black hexagon, used to draw a "shadow" on top of the hex being hovered over.
        /// </summary>
        Texture2D hexShadow;
        /// <summary>
        /// A hexagon that is entirely transparent except for its outline.
        /// </summary>
        Texture2D hexOutline;

        public enum ScrollDirections { Up, Down, Left, Right, None };
        ScrollDirections scrollDir = ScrollDirections.None;

        /// <summary>
        /// The number of Monster Displays being shown.
        /// </summary>
        public int MonsterDisplayCount {
            get {
                return _monsterDisplays.Count;
            }
        }

        /// <summary>
        /// Constructs a new MIMPGameScreenControl.
        /// </summary>
        public MIMPGameScreenControl() {
            _hexDisplays = new List<HexDisplay>();
            _monsterDisplays = new List<MonsterDisplay>();
            this.MouseClick += new System.Windows.Forms.MouseEventHandler(this_MouseClick);
            this.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this_MouseDoubleClick);
        }

        void this_MouseDoubleClick(object sender, MouseEventArgs e) {
            HexClicked(e);
        }

        void this_MouseClick(object sender, MouseEventArgs e) {
            HexClicked(e);
        }

        private void HexClicked(MouseEventArgs e) {
            Keys keys = Control.ModifierKeys;
            HexDisplay clicked = null;
            //first check if the click happened over a hex display
            foreach (HexDisplay hd in _hexDisplays) {
                hd.SetIsMouseOver(e.X, e.Y);
                if (hd.IsMouseOver) {
                    clicked = hd;
                }
            }
            //if it did, raise appropritate event
            if (clicked != null) {
                if (e.Button == MouseButtons.Left && e.Clicks == 1) {
                    RaiseHexLeftClicked(clicked.Hex, keys);
                } else if (e.Button == MouseButtons.Left && e.Clicks == 2) {
                    RaiseHexDoubleClicked(clicked.Hex, keys);
                } else if (e.Button == MouseButtons.Right && e.Clicks == 1) {
                    RaiseHexRightClicked(clicked.Hex, keys);
                }
            }
        }

        public void SetServices(ServiceProvider gameServices) {
            //get Services implementers
            IGameStateEventsService gameStateEvents = (IGameStateEventsService)(gameServices.GetService(typeof(IGameStateEventsService)));
            gameStateEvents.MonsterAdded += new EventHandler<EventArgs<Monster>>(gameStateEvents_MonsterAdded);
            gameStateEvents.MonsterRemoved += new EventHandler<EventArgs<Monster>>(gameStateEvents_MonsterRemoved);
            gameStateEvents.MonsterMoved += new EventHandler<EventArgs<Monster>>(gameStateEvents_MonsterMoved);
            gameStateEvents.SelectedMonsterChanged += new EventHandler<EventArgs<Monster>>(gameStateEvents_SelectedMonsterChanged);
            gameStateEvents.AttackersChanged += new EventHandler<EventArgs<List<Monster>>>(gameStateEvents_AttackersChanged);
            gameStateEvents.DefendersChanged += new EventHandler<EventArgs<List<Monster>>>(gameStateEvents_DefendersChanged);
            gameStateEvents.AttackTargetChosen += new EventHandler<EventArgs<Monster>>(gameStateEvents_AttackTargetChosen);
            gameStateEvents.ActionFailed += new EventHandler<EventArgs<string>>(gameStateEvents_ActionFailed);
            gameStateEvents.AttackCompleted += new EventHandler<EventArgs<List<Monster>>>(gameStateEvents_AttackCompleted);
            gameStateEvents.NewTurnBegan += new EventHandler<EventArgs<Player>>(gameStateEvents_NewTurnBegan);
            this.hexes = gameStateEvents.Hexes;
        }

        void gameStateEvents_NewTurnBegan(object sender, EventArgs<Player> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_NewTurnBegan");
            foreach (HexDisplay hd in _hexDisplays) {
                hd.Outlined = false;
                hd.OutlineColor = Color.Black;
            }
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_NewTurnBegan");
        }

        void gameStateEvents_AttackCompleted(object sender, EventArgs<List<Monster>> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_AttackCompleted");
            foreach (HexDisplay hd in _hexDisplays) {
                hd.Outlined = false;
                hd.OutlineColor = Color.Black;
            }
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_AttackCompleted");
        }

        void gameStateEvents_ActionFailed(object sender, EventArgs<string> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_ActionFailed");
            foreach (HexDisplay hd in _hexDisplays) {
                hd.Outlined = false;
                hd.OutlineColor = Color.Black;
            }
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_ActionFailed");
        }

        void gameStateEvents_AttackTargetChosen(object sender, EventArgs<Monster> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_AttackTargetChosen");
            if (log.IsDebugEnabled) log.Debug("Attack target chosen: " + e.EventData);
            HexDisplay currentlyTargeted = _hexDisplays.Find(
                delegate(HexDisplay hd) { return hd.Outlined && hd.OutlineColor == Constants.COLOR_TARGET; });
            if (currentlyTargeted != null) {
                currentlyTargeted.Outlined = false;
                currentlyTargeted.OutlineColor = Color.Black;
            }

            HexDisplay newTargeted = _hexDisplays.Find(
                delegate(HexDisplay hd) { return hd.Hex.MonsterOn == e.EventData; });
            if (newTargeted != null) {
                newTargeted.Outlined = true;
                newTargeted.OutlineColor = Constants.COLOR_TARGET;
            }
            this.Invalidate();
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_AttackTargetChosen");
        }

        void gameStateEvents_SelectedMonsterChanged(object sender, EventArgs<Monster> e) {

            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_SelectedMonsterChanged");
            if (log.IsDebugEnabled) log.Debug("Selected monster changed to: " + e.EventData);
            HexDisplay currentlySelected = _hexDisplays.Find(
                delegate(HexDisplay hd) { return hd.Outlined && hd.OutlineColor == Constants.COLOR_SELECTED; });
            if (currentlySelected != null) {
                currentlySelected.Outlined = false;
                currentlySelected.OutlineColor = Color.Black;
            }
            
            HexDisplay newSelected = _hexDisplays.Find(
                delegate(HexDisplay hd) { return hd.Hex.MonsterOn == e.EventData; });
            if (newSelected != null) {
                newSelected.Outlined = true;
                newSelected.OutlineColor = Constants.COLOR_SELECTED;
            }
            this.Invalidate();
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_SelectedMonsterChanged");
        }

        void gameStateEvents_AttackersChanged(object sender, EventArgs<List<Monster>> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_AttackersChanged");
            if (log.IsDebugEnabled) log.Debug("There are now " + e.EventData.Count + " attackers.");

            List<HexDisplay> oldattackers = _hexDisplays.FindAll(
                delegate(HexDisplay hd) { return hd.OutlineColor == Constants.COLOR_ATTACKER; });

            //reset all attacker outlines
            foreach (HexDisplay disp in oldattackers) {
                disp.OutlineColor = Color.Black;
                disp.Outlined = false;
            }
            
            //set new outlines
            List<HexDisplay> newattackers = _hexDisplays.FindAll(
                delegate(HexDisplay h) { return h.Hex.IsMonsterOn && e.EventData.Contains(h.Hex.MonsterOn); });
            foreach (HexDisplay disp in newattackers) {
                disp.Outlined = true;
                disp.OutlineColor = Constants.COLOR_ATTACKER;
            }


            this.Invalidate();
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_AttackersChanged");
        }

        void gameStateEvents_DefendersChanged(object sender, EventArgs<List<Monster>> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_DefendersChanged");
            if (log.IsDebugEnabled) log.Debug("There are now " + e.EventData.Count + " attackers.");

            List<HexDisplay> olddefenders = _hexDisplays.FindAll(
                delegate(HexDisplay hd) { return hd.OutlineColor == Constants.COLOR_DEFENDER; });

            //reset all defender outlines
            foreach (HexDisplay disp in olddefenders) {
                disp.OutlineColor = Color.Black;
                disp.Outlined = false;
            }

            //set new outlines
            List<HexDisplay> newdefenders = _hexDisplays.FindAll(
                delegate(HexDisplay h) { return h.Hex.IsMonsterOn && e.EventData.Contains(h.Hex.MonsterOn); });
            foreach (HexDisplay disp in newdefenders) {
                disp.Outlined = true;
                disp.OutlineColor = Constants.COLOR_DEFENDER;
            }

            this.Invalidate();
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_DefendersChanged");
        }

        /// <summary>
        /// Starts scrolling the game screen in the direction indicated.
        /// Passing ScrollDirections.None stops the scrolling (it is equivalent to calling StopScrolling).
        /// </summary>
        public void StartScrolling(ScrollDirections direction) {
            if (log.IsInfoEnabled) log.Info("Entered StartScrolling");
            if (log.IsDebugEnabled) log.Debug("Scoll direction: " + Enum.GetName(typeof(ScrollDirections), direction));
            scrollDir = direction;
            if (log.IsInfoEnabled) log.Info("Exiting StartScrolling");
        }

        /// <summary>
        /// Stops scrolling the game screen.
        /// </summary>
        public void StopScrolling() {
            if (log.IsInfoEnabled) log.Info("Entered StopScrolling");
            scrollDir = ScrollDirections.None;
            if (log.IsInfoEnabled) log.Info("Exiting StopScrolling");
        }

        void gameStateEvents_MonsterRemoved(object sender, EventArgs<Monster> e) {
            MonsterDisplay deleted = _monsterDisplays.Find(delegate(MonsterDisplay md) { return md.Monster.Equals(e.EventData); });
            _monsterDisplays.Remove(deleted);
            this.Invalidate();
        }

        void gameStateEvents_MonsterAdded(object sender, EventArgs<Monster> e) {
            HexDisplay hd = _hexDisplays.Find(delegate(HexDisplay h) { return h.Hex == e.EventData.Location; });
            if (hd != null) {
                SetupMonsterDisplay(e.EventData, hd);
                this.Invalidate();
            }
        }

        void gameStateEvents_MonsterMoved(object sender, EventArgs<Monster> e) {
            if (log.IsInfoEnabled) log.Info("Entered gameStateEvents_MonsterMoved");
            //clear selected hex outline
            HexDisplay oldSelected = _hexDisplays.Find(delegate(HexDisplay hexd) { return hexd.OutlineColor == Constants.COLOR_SELECTED; });
            if (oldSelected != null) {
                oldSelected.OutlineColor = Color.Black;
                oldSelected.Outlined = false;
            }

            MonsterDisplay md = _monsterDisplays.Find(delegate(MonsterDisplay m) { return m.Monster.Equals(e.EventData); });
            HexDisplay hd = _hexDisplays.Find(delegate(HexDisplay h) { return h.Hex == e.EventData.Location; });
            if (hd != null && md != null) {
                if (log.IsDebugEnabled) log.Debug("Moving monster display " + md + " to " + hd);
                CenterMonsterDisplayInHexDisplay(md, hd);
                this.Invalidate();
            }
            if (log.IsInfoEnabled) log.Info("Exiting gameStateEvents_MonsterMoved");
        }
        /// <summary>
        /// Loads content needed by the GameplayScreen.
        /// </summary>
        /// <param name="content"></param>
        private void LoadContent() {
            controlPointSprite = content.Load<Texture2D>(Constants.CONTENT_CONTROL_POINT_ICON);
            hexShadow = content.Load<Texture2D>(Constants.CONTENT_HEXAGON_SHADOW);
            hexOutline = content.Load<Texture2D>(Constants.CONTENT_HEXAGON_OUTLINE);
            SetupHexDisplays();
        }

        /// <summary>
        /// Helper for LoadContent(). Instantiates the HexDisplay objects in HexDisplays.
        /// </summary>
        private void SetupHexDisplays() {
            try {
                Texture2D hexagon = content.Load<Texture2D>(Constants.CONTENT_HEXAGON);
                int startX = 15;
                int startY = 15;

                foreach (Hex hex in hexes) {
                    int x = (int)Math.Floor(startX + hex.Col * (hexagon.Width * 0.75));
                    int y = (int)Math.Ceiling((double)startY + hex.Row * hexagon.Height);

                    if (hex.Col % 2 == 1) {
                        y += (int)Math.Ceiling(hexagon.Height / 2.0);
                    }
                    HexDisplay hd = new HexDisplay(hex, x, y, hexagon, hexOutline, hexShadow);
                    _hexDisplays.Add(hd);
                    if (hex.IsMonsterOn) {
                        SetupMonsterDisplay(hex.MonsterOn, hd);
                    }
                }
            } catch (System.IO.FileNotFoundException fnfe) {
                throw fnfe;
            }
        }
        /// <summary>
        /// Helper for LoadContent(). Instantiates the MonsterDisplay objects in MonsterDisplays.
        /// </summary>
        private void SetupMonsterDisplay(Monster monster, HexDisplay location) {
            MonsterDisplay md;
            Texture2D mSprite = null;
            try {
                mSprite = content.Load<Texture2D>(Constants.MONSTER_ICON_DIRECTORY + monster.IconName);

            } catch (System.IO.FileNotFoundException) {
                mSprite = content.Load<Texture2D>(Constants.CONTENT_DEFAULT_MONSTER);
            } finally {
                Texture2D copy = new Texture2D(mSprite.GraphicsDevice, mSprite.Width, mSprite.Height, mSprite.LevelCount, mSprite.TextureUsage, mSprite.Format);
                Color[] arr = new Color[mSprite.Width * mSprite.Height];
                mSprite.GetData<Color>(arr);
                copy.SetData<Color>(arr);
                md = new MonsterDisplay(monster, 1, copy);
                md.ReplaceColorInSprite(Color.White, monster.Controller.Color);
                CenterMonsterDisplayInHexDisplay(md, location);
                _monsterDisplays.Add(md);
            }
        }

        private void CenterMonsterDisplayInHexDisplay(MonsterDisplay toCenter, HexDisplay target) {
            int centerX = (int)target.Origin.X + (target.Sprite.Width - toCenter.Sprite.Width) / 2;
            int centerY = (int)target.Origin.Y + (target.Sprite.Height - toCenter.Sprite.Height) / 2;
            toCenter.Origin = new Vector2(centerX, centerY);
        }

        /// <summary>
        /// Draw all HexDisplays and ControlPoints in the game.
        /// </summary>
        private void DrawHexDisplays(SpriteBatch spriteBatch) {
            if (log.IsInfoEnabled) log.Info("Entered DrawHexDisplays");
            if (log.IsDebugEnabled) log.Debug("Drawing " + _hexDisplays.Count + " hex displays.");
            foreach (HexDisplay hd in _hexDisplays) {             
                hd.Draw(spriteBatch);

                if (hd.Hex.HasControlPoint) {
                    if (log.IsDebugEnabled) log.Debug("Drawing a control point at " + hd.Hex);
                    int centerX = (int)hd.Origin.X + (hd.Sprite.Width - controlPointSprite.Width) / 2;
                    int centerY = (int)hd.Origin.Y - 2;
                    Color cpColor = hd.Hex.ControlPoint.Controller != null ? hd.Hex.ControlPoint.Controller.Color : Color.Gray;
                    spriteBatch.Draw(controlPointSprite, new Vector2(centerX, centerY), cpColor);
                }
            }
            if (log.IsInfoEnabled) log.Info("Exiting DrawHexDisplays");
        }
        /// <summary>
        /// Draw all MonsterDisplays in the game.
        /// </summary>
        private void DrawMonsterDisplays(SpriteBatch spriteBatch) {
            if (log.IsInfoEnabled) log.Info("Entered DrawMonsterDisplays");
            if (log.IsDebugEnabled) log.Debug("Drawing " + _monsterDisplays.Count + " monsters.");
            foreach (MonsterDisplay md in _monsterDisplays) {
                md.Draw(spriteBatch);
            }
            if (log.IsInfoEnabled) log.Info("Exiting DrawMonsterDisplays");
        }

        protected override void Initialize() {
            content = new ContentManager(Services, "Content");
            spriteBatch = new SpriteBatch(GraphicsDevice);
            LoadContent();
        }

        protected override void GameUpdate() {
            if (scrollDir != ScrollDirections.None) {
                if (log.IsDebugEnabled) log.Debug("Scrolling direction: " + Enum.GetName(typeof(ScrollDirections), scrollDir));
                int deltaX = 0;
                int deltaY = 0;
                switch (scrollDir) {
                    case ScrollDirections.Down:
                        deltaY = -4;
                        break;
                    case ScrollDirections.Up:
                        deltaY = 4;
                        break;
                    case ScrollDirections.Left:
                        deltaX = -4;
                        break;
                    case ScrollDirections.Right:
                        deltaX = 4;
                        break;
                }

                Vector2 delta = new Vector2(deltaX, deltaY);
                foreach (HexDisplay hd in _hexDisplays) {
                    hd.Origin += delta;
                }
                foreach (MonsterDisplay md in _monsterDisplays) {
                    md.Origin += delta;
                }

                this.Invalidate();
            }
        }


        protected override void Draw() {
            if (log.IsInfoEnabled) log.Info("Entered Draw");
            GraphicsDevice.Clear(Color.DarkSlateGray);

            spriteBatch.Begin();
            DrawHexDisplays(spriteBatch);
            DrawMonsterDisplays(spriteBatch);
            spriteBatch.End();
            if (log.IsInfoEnabled) log.Info("Exiting Draw");
        }
    }
}

